package com.chenfan.filestore.mq.task;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.converters.biginteger.BigIntegerStringConverter;
import com.alibaba.excel.converters.date.DateStringConverter;
import com.alibaba.excel.converters.integer.IntegerStringConverter;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.chenfan.filestore.config.DS;
import com.chenfan.filestore.config.DynamicDataSourceContextHolder;
import com.chenfan.filestore.config.FileStoreConfig;
import com.chenfan.filestore.model.BatchExportTemplate;
import com.chenfan.filestore.model.Task;
import com.chenfan.filestore.service.BatchExportTemplateService;
import com.chenfan.filestore.service.FileService;
import com.chenfan.filestore.service.TaskService;
import com.chenfan.filestore.utils.FileUtil;
import com.chenfan.filestore.utils.ToStringUtil;
import com.chenfan.filestore.utils.excel.DoubleStringConverter;
import com.chenfan.filestore.utils.excel.ImageHandler;
import com.chenfan.filestore.utils.excel.SqlDateConverter;
import com.chenfan.filestore.utils.excel.TimestampConverter;
import com.chenfan.filestore.vo.FileResult;
import com.chenfan.infra.context.TenantContextHolder;
import com.chenfan.websocket.api.WebSocketClient;
import com.chenfan.websocket.api.dto.WebSocketMessageDTO;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter;
import groovy.lang.Tuple2;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static com.chenfan.filestore.common.Constants.BATCH_EXPORT_TASK_TOPIC;

@Service
@Slf4j
@RocketMQMessageListener(topic = BATCH_EXPORT_TASK_TOPIC, consumeThreadMax = 5, consumerGroup = "${spring.application.name}_export_group")
public class TaskConsumer extends BaseMqConsumer implements RocketMQListener<TaskMqMsg> {

    @Autowired
    private FileStoreConfig fileStoreConfig;

    @Autowired
    private TaskService taskService;

    @Autowired
    private BatchExportTemplateService batchExportTemplateService;


    @Autowired
    private FileService fileService;

    @Autowired
    private WebSocketClient webSocketClient;

    @Override
    @DS
    public void onMessage(TaskMqMsg taskMqMsg) {
        try {
            TenantContextHolder.setTenantId(ObjectUtils.defaultIfNull(taskMqMsg.getTenantId(), 1L));
            triggerTask(taskMqMsg.getTaskId());
        } finally {
            TenantContextHolder.clear();
        }
    }

    public void triggerTask(Long taskId) {
        Task task = taskService.getById(taskId);
        if (Objects.isNull(task)) {
            log.error("任务id为空，taskId={}",taskId);
            return;
        }
        try {
            // 1、更新任务为待执行，更新执行时间、记录总数
            task = updateTaskStart(task);

            // 2、执行excel导出 实时更新任务状态及数量
            String absoluteLocalFilePath = doExportFile2Local(task);

            // 3、上传excel文件 返回 文件相对url

            FileResult fileResult = fileService.uploadFile(new File(absoluteLocalFilePath));

            //设置下载url文件地址
            task.setDownFileUrl(Objects.isNull(fileResult.getId())?"":fileResult.getId());

            // 4、更新执行为成功
            task.setAlreadyCount(task.getTotalCount());
            task.setStatus(Task.STATUS_SUCCESS);
            updateTaskFinish(task);
        } catch (Throwable e){
            log.error("消费导出任务失败",e);
            // 执行失败记录更新执行结果
            task.setStatus(Task.STATUS_FAIL);
            task.setStackTrace(e.getMessage());
            updateTaskFinish(task);
        }
        DynamicDataSourceContextHolder.clear();

        push2Client(task);
    }

    private void push2Client(Task task){
        try {
            Map<String,Object> downloadInfo = new HashMap<>();
            downloadInfo.put("fileName",task.getFileName());
            downloadInfo.put("downFileUrl",task.getDownFileUrl());
            WebSocketMessageDTO msg = new WebSocketMessageDTO();
            msg.setMsgType("1");
            msg.setUserId(task.getCreateBy());
            msg.setData(downloadInfo);
            webSocketClient.websocketPush(msg);
        }catch (Throwable e){
            log.error("推送websocket消息失败{},堆栈{}",task,e);
        }

    }

    private String doExportFile2Local(Task task) throws IOException {
        BatchExportTemplate template = batchExportTemplateService.getById(task.getTemplateId());
        String localParentDir = new StringBuffer(fileStoreConfig.getTempPath()).append(FileUtil.getUploadDir()).toString();
        String newLocalFilePath = new StringBuffer(localParentDir).append(FileUtil.uuid()+".").append(FileUtil.suffix(template.getTemplateUrl())).toString();
        FileUtil.createParentDir(localParentDir);

        if (newLocalFilePath.endsWith(".csv")) {
            csvWrite(task, template, newLocalFilePath);
        } else {
            excelWrite(task, template, newLocalFilePath);
        }

        return newLocalFilePath;
    }

    private void excelWrite(Task task, BatchExportTemplate template, String newLocalFilePath) throws IOException {
        ImageHandler imageHandler = new ImageHandler();
        imageHandler.setBatchImageSize(fileStoreConfig.getBatchImageSize());
        imageHandler.setFilePrefixPath(fileStoreConfig.getFilePrefixPath());
        //小于1000并且参数有传true才解析图片
        imageHandler.setShowPic(task.isShowImage() && (task.getTotalCount()<= 2000) ? true : false);

        // 远程获取文件流 todo leo 待做本地缓存 提高效率
        InputStream remoteTemplate = fileService.getInputStream(template.getTemplateUrl());
        ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(newLocalFilePath)
                .registerWriteHandler(imageHandler)
                .registerConverter(new IntegerStringConverter())
                .registerConverter(new BigIntegerStringConverter())
                .registerConverter(new DoubleStringConverter())
                .registerConverter(new DateStringConverter())
                .registerConverter(new LongStringConverter())
                .registerConverter(new TimestampConverter())
                .excelType(template.getTemplateUrl().endsWith(".xls")? ExcelTypeEnum.XLS : ExcelTypeEnum.XLSX)
                .registerConverter(new SqlDateConverter())
                .withTemplate(remoteTemplate);
        ExcelWriter excelWriter = excelWriterBuilder.build();

        //  todo leo 多个sheet页面 待定多少个sheet 模板设置名称失效
        WriteSheet writeSheet = EasyExcel.writerSheet(0,"1111111").build();
        FillConfig fillConfig = FillConfig.builder().build();
        List<Object> datas = new ArrayList<>();
        DynamicDataSourceContextHolder.set(template.getDatabaseId()+"");
        log.info("使用database={}",template.getDatabaseId());
        if(0 != task.getTotalCount().intValue()){
            taskService.findByBatch(task.getSql(), context -> {
                Object dbMapData = context.getResultObject();
                //分批写入
                datas.add(dbMapData);
//                // 大于1000条时图片默认不解析
//                if(context.getResultCount() > 1000 && isOver1000){
//                    imageHandler.setShowPic(false);
//                    excelWriter.fill(datas,fillConfig,writeSheet);
//                }
                if(context.getResultCount()% 10000 == 9999 || task.getTotalCount().intValue() == context.getResultCount()){
                    excelWriter.fill(datas,fillConfig,writeSheet);
                    task.setAlreadyCount(Long.valueOf(context.getResultCount()));
                    datas.clear();
                }
            });

        } else{
            excelWriter.fill(datas,fillConfig,writeSheet);
        }
        excelWriter.finish();
        DynamicDataSourceContextHolder.clear();
        remoteTemplate.close();
    }

    private void csvWrite(Task task, BatchExportTemplate template, String newLocalFilePath) throws IOException {
        Tuple2<String[], Map<String, String>> tuple = parseHeaders(template);
        String[] headers = tuple.getFirst();
        Map<String, String> values = tuple.getSecond();

        CsvWriter csvWriter = new CsvWriter(newLocalFilePath, ',', StandardCharsets.UTF_8);
        csvWriter.writeRecord(headers);
        List<Object> datas = new ArrayList<>();

        if(0 != task.getTotalCount().intValue()){
            DynamicDataSourceContextHolder.set(template.getDatabaseId()+"");
            taskService.findByBatch(task.getSql(), context -> {
                Object dbMapData = context.getResultObject();
                //分批写入
                datas.add(dbMapData);
                if(context.getResultCount()% 10000 == 9999 || task.getTotalCount().intValue() == context.getResultCount()){
                    task.setAlreadyCount(Long.valueOf(context.getResultCount()));
                    writeDataToCsv(csvWriter, headers, values, datas);
                    datas.clear();
                }
            });
        } else {
            writeDataToCsv(csvWriter, headers, values, datas);
        }
        csvWriter.flush();
        csvWriter.close();
    }

    private void writeDataToCsv(CsvWriter csvWriter, String[] headers, Map<String, String> values, List<Object> datas) {
        datas.forEach(e -> {
            // 构建数据
            String[] record = new String[headers.length];
            for (int i = 0; i < headers.length; i++) {
                JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(e, SerializerFeature.WriteDateUseDateFormat, SerializerFeature.WriteNonStringValueAsString));
                ToStringUtil.replaceData(jsonObject);
                record[i] = jsonObject.getString(values.get(headers[i]));
            }

            try {
                csvWriter.writeRecord(record);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        });
    }

    private Tuple2<String[], Map<String, String>> parseHeaders(BatchExportTemplate template) throws IOException {
        String path = String.format("%s/%s", "/Users/liuming/Downloads", template.getTemplateUrl());
        CsvReader csvReader = new CsvReader(path, ',', Charset.forName("GBK"));
        csvReader.readHeaders();

        String[] headers = csvReader.getHeaders();
        Map<String, String> map = new HashMap<>();
        // 读取每行的内容
        while (csvReader.readRecord()) {
            // 获取内容的两种方式
            for (int i = 0; i < headers.length; i++) {
                String key = csvReader.get(i).replace("{.", "").replace("}", "");
                map.put(new String(headers[i].getBytes(StandardCharsets.UTF_8)), key);
            }
        }
        return new Tuple2<>(headers, map);
    }

    /**
     * 更新最后的结果
     * @param task
     */
    private void updateTaskFinish(Task task) {
        DynamicDataSourceContextHolder.set(DS.MAIN);
        task.setDoneTime(new Date());
        task.setUpdateDate(new Date());
        taskService.updateById(task);
    }

    /**
     * 更新初始执行状态
     * @param task
     * @return
     */
    private Task updateTaskStart(Task task) {
        //放置于最前 放置动态数据源出现异常导致没有设置值
        task.setDoTime(new Date());
        task.setStatus(Task.STATUS_EXECUTING);
        taskService.updateById(task);
        BatchExportTemplate exportTemplate = batchExportTemplateService.getById(task.getTemplateId());
        if(Objects.nonNull(task.getTaskId()) && Objects.nonNull(task.getSql())){
            DynamicDataSourceContextHolder.set(exportTemplate.getDatabaseId()+"");
            Long count = taskService.findByCount(task.getSql());
            DynamicDataSourceContextHolder.clear();
            task.setTotalCount(count);
            taskService.updateById(task);
        }
        return task;
    }
}
